/*global $*/

$(function() {
    function getInternetExplorerVersion()
    // Returns the version of Internet Explorer or a -1
    // (indicating the use of another browser).
    {
      var rv = -1; // Return value assumes failure.
      if (navigator.appName == 'Microsoft Internet Explorer')
      {
        var ua = navigator.userAgent;
        var re  = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
        if (re.exec(ua) != null)
          rv = parseFloat( RegExp.$1 );
      }
      return rv;
    }
    function checkVersion()
    {
      var msg = "You're not using Internet Explorer.";
      var ver = getInternetExplorerVersion();
    
      if ( ver > -1 )
      {
      $("<div id='dialog-modal' class='ui-state-highlight' title='You are Using Internet Explorer'><p ><span class='ui-state-error'>Warning<\/span>: Lock5 StatApps run sluggishly on Internet Explorer.<\/p><p>Consider using these applications with <a href='http://www.google.com/chrome'>Chrome</a> (best) or <a href='http://www.mozilla.com/en-US/firefox/'>Firefox<\/a>.<\/p><\/div>").dialog({
          modal: true,
          buttons: {
              "Ok": function() {
                  $(this).dialog("close");
              }
          }
      });
      }
    }
    checkVersion();
});

Math.truncate = function(n, places) {
    var value = n.toFixed(places);
    if (value === (0).toFixed(places)) {
        return n.toPrecision(places);
    }
    return n.toFixed(places);
};
var LOCK5MAIN = {
    /**
     * ORIGINAL_SAMPLE: object that's used to keep track of the dataset that
     * generates samples
     */
    ORIGINAL_SAMPLE: {},
    /**
     * PIXELSPERDOT: global describing how densely to plot the dotplot
     */
    PIXELSPERDOT: 7,

    /**
     * MINPAGEWIDTH: the minimum desired width of the application on a page
     */
    MINPAGEWIDTH: 700,

    /**
     * MINSCATTERHEIGHT: the minimum desired height for a scatterplot
     */
    MINSCATTERHEIGHT: 150,
    /*
     * LOCK5MAIN.generateGraphBounds: used to generate default graph bounds within
     * the given xyPoints
     *
     * returns: Object {xaxis,yaxis,grid} in the same format as the $.plot package
     * requires
     *
     * parameters: xyPoints (Array): an array whose elements are length 2 arrays of
     * floats extras (Object): extra name value pairs to merge with the generated
     * properties
     */
    generateGraphBounds: function(xyPoints, extras) {
        function pad(range, percent) {
            var h = range.max - range.min;
            return {
                max: range.max + h * percent,
                min: range.min - h * percent
            };
        }

        var properties = {
            xaxis: pad($.findMinMax(xyPoints, 0), 0.025),
            yaxis: pad($.findMinMax(xyPoints, 1), 0.025),
            grid: {
                tickColor: "#eee",
                hoverable: true
            }
        };

        $.extend(properties.xaxis, extras.xaxis);
        $.extend(properties.yaxis, extras.yaxis);

        return properties;
    },
    hoverPoint: ( function() { // function to animate hovering over
        // a point so everything is smooth
        // make a module that remembers the previous point
        var previousPoint = null;
        return function(event, pos, item) {
            if (item) {
                // the next set of logic is necessary so that hovering
                // over the same point doesn't cause flicker
                if (!previousPoint || previousPoint[0] !== item.datapoint[0] || previousPoint[1] !== item.datapoint[1]) {
                    var x = item.datapoint[0].toFixed(0), y = item.datapoint[1].toFixed(0);
                    $("#tooltip").remove();
                    LOCK5MAIN.showTooltip(item.pageX, item.pageY, "(" + x + "," + y + ")");
                    previousPoint = item.datapoint;
                }
            } else {
                $("#tooltip").remove();
                previousPoint = null;
            }
        };
    }()),
    hoverXValue: ( function() { // function to animate hovering over
        // a point so everything is smooth
        // make a module that remembers the previous point
        var previousPoint = null;
        return function(event, pos, item) {
            if (item) {
                // the next set of logic is necessary so that hovering
                // over the same point doesn't cause flicker
                if (!previousPoint || previousPoint[0] !== item.datapoint[0] || previousPoint[1] !== item.datapoint[1]) {
                    var x = item.series.data[item.dataIndex][2].toFixed(3);
                    $("#tooltip").remove();
                    LOCK5MAIN.showTooltip(item.pageX, item.pageY, "(" + x + ")");
                    previousPoint = item.datapoint;
                }
            } else {
                $("#tooltip").remove();
                previousPoint = null;
            }
        };
    }()),
    formatLine: function(data) { // used to format internal
        // format to one used by flot
        return {
            data: data.bounds,
            lines: {
                show: true,
                lineWidth: 1
            },
            color: "#F00",
            shadowSize: 0,
            label: "slope=" + (data.b >= 0 ? "+" : "") + data.b.toFixed(2),
            hoverable: false
        };
    },
    formatPoints: function(points, color, label) { // used to
        // format points to one used by flot
        color = color || "#000";
        label = label || null;
        return {

            data: points || [],
            label: label,
            points: {
                show: true,
                radius: 1,
                fill: true,
                fillColor: "#000"
            },
            color: color,
            shadowSize: 1
        };
    },
    //      plots given a graphID, points, and optional properties
    plot: function(graphId, xyPoints, graphProperties) {
        var slope, thePlot, linearRegressionLine = {}, points, r, corelationLabel = "", nPointsLabel = "";
        points = LOCK5MAIN.formatPoints(xyPoints);
        if (xyPoints && xyPoints.length > 1) {
            slope = $.calculateLinearRegression(xyPoints);
            linearRegressionLine = LOCK5MAIN.formatLine($.calculateLinearRegression(xyPoints));
            r = $.calculateCorrelation(xyPoints);
            
            corelationLabel = LOCK5MAIN.formatPoints([], "#f00", "Correlation = " + Math.truncate(r, 3));
            nPointsLabel = LOCK5MAIN.formatPoints([], "#f00", "n = " + xyPoints.length);
        }
        delete linearRegressionLine.label;
        thePlot = $.plot($(graphId), [ points, linearRegressionLine ], graphProperties || LOCK5MAIN
        .generateGraphProperties(xyPoints));
        if (xyPoints.length > 0) {
            if ($("#stats",$(graphId).parent()).length === 0) {
                $(graphId).before($("<div id='stats' class='stats'><\/div>"));
            }
            if ($("h3",$(graphId).parent()).html().substring(0,1) === "O") {
                $("#stats",$(graphId).parent()).addClass("small").html("$slope=" + (slope.b >= 0 ? "+" : "") + slope.b.toFixed(2) + "\\ r = " + Math.truncate(r, 3) + " " + "\\ n = " + xyPoints.length + "$");
            } else {
                $("#stats",$(graphId).parent()).addClass("small").html("$slope=" + (slope.b >= 0 ? "+" : "") + slope.b.toFixed(2) + "\\ r = " + Math.truncate(r, 3) + " " + "\\ n = " + xyPoints.length + "$");
            }
            MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
            
        }
        return thePlot;
    },
    //      plots two datasets, one on top of the other
    doublePlot: function(graphId, xyPoints, options) {
        var thePlot, points, r, corelationLabel = "", nPointsLabel = "",
        graphHeight, graphWidth, graphProperties, histoPoints = [ [], [] ],
        k, maxBucket, means = [0,0], i, o;
        if (xyPoints.length > 0) {
            graphHeight = parseInt($(graphId).height(), 10);
            graphWidth = parseInt($(graphId).width(), 10);

            if (LOCK5MAIN.doublePlotProperties && (!options || options.useOldAxes)) {
                graphProperties = LOCK5MAIN.doublePlotProperties;
            } else {
                graphProperties = {
                    xaxis: {
                        // histogram sorts points backwards
                        min: $.min([ $.min(xyPoints[0]), $.min(xyPoints[1]) ]),
                        max: $.max([ $.max(xyPoints[0]), $.max(xyPoints[1]) ])
                    },
                    grid: {
                        tickColor: "#eee",
                        hoverable: true
                    }
                };
                
                LOCK5MAIN.doublePlotProperties = graphProperties;
            }
            
            // make histogram
            k = Math.floor(graphWidth / LOCK5MAIN.PIXELSPERDOT);

            maxBucket = 0;
            means = [0,0];
            $.each(xyPoints, function(i, points) {
                var bucketCount = [];
                $.each(points, function(j, val) {
                    // calculate bucket
                    var b = Math.floor((val - graphProperties.xaxis.min) / (graphProperties.xaxis.max - graphProperties.xaxis.min) * k);
                    if (!bucketCount[b]) {
                        bucketCount[b] = 0;
                    }
                    histoPoints[i][j] = [ graphProperties.xaxis.min+b*(graphProperties.xaxis.max-graphProperties.xaxis.min)/k, bucketCount[b], val];
                    bucketCount[b] = bucketCount[b] + 1;
                    if (maxBucket < bucketCount[b]) {
                        maxBucket = bucketCount[b];
                    }
                    means[i] = means[i]+val;
                });
                means[i]=means[i]/points.length;
            });
            
            for (i = 0; i < histoPoints[1].length; i=i+1) {
                histoPoints[1][i][1] = histoPoints[1][i][1]+maxBucket;
            }
            graphProperties.yaxis = {
                min: 0,
                max: Math.floor(parseInt($("#original-sample-plot").height(), 10) / LOCK5MAIN.PIXELSPERDOT),
                ticks: [] //turn off axis
            };
            
            if (true || graphProperties.yaxis.max < maxBucket*2.1) {
                graphProperties.yaxis.max = maxBucket*2.1;
            }

            histoPoints[0] = LOCK5MAIN.formatPoints(histoPoints[0], "#69580B", null);
            histoPoints[1] = LOCK5MAIN.formatPoints(histoPoints[1], "#690651", null);

        } else {
            graphProperties = {
                xaxis: {
                    // histogram sorts points backwards
                    min: $.min([ $.min(LOCK5MAIN.ORIGINAL_SAMPLE.data[0]), $.min(LOCK5MAIN.ORIGINAL_SAMPLE.data[1]) ]),
                    max: $.max([ $.max(LOCK5MAIN.ORIGINAL_SAMPLE.data[0]), $.max(LOCK5MAIN.ORIGINAL_SAMPLE.data[1]) ])
                },
                yaxis: {
                    ticks:[]
                }
            };
        }
        thePlot = $.plot($(graphId), [histoPoints[0], histoPoints[1],{
            data:[],
            color:"#CC0"
        }], graphProperties);
            
        if (xyPoints.length > 0) {
            if ($("#stats",$(graphId).parent()).length === 0) {
                $(graphId).before($("<div id='stats' class='stats'><\/div>"));
            }
            $("#stats",$(graphId).parent()).addClass("small").html("$\\overline{x}_1-\\overline{x}_2="+Math.truncate(means[0]-means[1],2)+ ",\\ n_1 = " + histoPoints[0].data.length + ",\\ n_2 = " + histoPoints[1].data.length + "$");
            MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
            $.each([{
                mean: means[0],
                color: "#CC0",
                y: 0
            },{
                mean: means[1],
                color: "#CC0",
                y: maxBucket*1.1
            }], function(i,mean) {
                var offset, arrowHeight, arrowWidth, textWidth, canvasContext,
                text,color;
                text = Math.truncate(mean.mean,3);
                color = mean.color;
                arrowHeight = 20;
                arrowWidth = 6;
                offset = thePlot.pointOffset({
                    x: mean.mean,
                    y: mean.y
                });

                textWidth = $.calculateSize(text).width;

                thePlot.getPlaceholder().append("<div class=\"arrow-label\" class=\"ui-dialog-content\" style=\"position:absolute;left:" + (offset.left - textWidth / 2) + "px;top:" + (offset.top + arrowHeight) + "px;\">" + text + "<\/div>");

                //plot subplot labels
                $(graphId).append("<div class=\"graph-label\" class=\"ui-dialog-content\" style=\"position:absolute;-webkit-transform: rotate(-90deg);-moz-transform: rotate(-90deg); filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);left:-"+$.calculateSize(LOCK5MAIN.ORIGINAL_SAMPLE.label[0]).width*0.8+"px;top:" + ($(graphId).height()*.7) + "px;\">" + LOCK5MAIN.ORIGINAL_SAMPLE.label[0] + "<\/div>");
                $(graphId).append("<div class=\"graph-label\" class=\"ui-dialog-content\" style=\"position:absolute;-webkit-transform: rotate(-90deg);-moz-transform: rotate(-90deg); filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);left:-"+$.calculateSize(LOCK5MAIN.ORIGINAL_SAMPLE.label[1]).width*0.8+"px;top:" + ($(graphId).height()*.3) + "px;\">" + LOCK5MAIN.ORIGINAL_SAMPLE.label[1] + "<\/div>");

                // This part does the little arrow drawing
                canvasContext = thePlot.getCanvas().getContext("2d");
                canvasContext.beginPath();
                canvasContext.moveTo(offset.left, offset.top);
                canvasContext.lineTo(offset.left - arrowWidth / 2, offset.top + arrowHeight);
                canvasContext.lineTo(offset.left + arrowWidth / 2, offset.top + arrowHeight);
                canvasContext.lineTo(offset.left, offset.top);
                canvasContext.fillStyle = color;
                canvasContext.fill();
            });
        }
        $(".legendColorBox",$(graphId)).show();

        ( function() {

            $(graphId + " div.legend").children().hover( function(e) {
                $(this).hide();
            });
            $(graphId + " div.arrow-label").hover( function(e) {
                $(this).hide();
            });
            $(graphId).hover(null, function(e) {
                $(graphId + " div.legend").children().show();
                $(graphId + " div.arrow-label").show();
            });
        }());
        return thePlot;
    },
    //      plots two datasets, one on top of the other
    singlePlot: function(graphId, xyPoints, options) {
        var thePlot, points, r, corelationLabel = "", nPointsLabel = "",
        graphHeight, graphWidth, graphProperties, histoPoints = [ ],
        k, bucketCount=[],maxBucket, means = [0,0], i, o, meanFn, stdevFn, tmpPoints, minmax, mean;
        
        meanFn = function(array) {
            var mean = 0, i;
            for (i = 0; i < array.length; i+= 1) {
                mean += array[i];
            }
            return mean/array.length;
        };
        stdevFn = function(arr) {
            if (arr.length <= 1) {
                return NaN;
            }
            var sum = 0, mean = meanFn(arr), i;
            for (i = 0; i < arr.length; i+= 1) {
                sum += Math.pow(arr[i] - mean, 2);
            }
            return Math.sqrt(sum / (arr.length - 1));
        };
        
        if (xyPoints.length > 0) {
            if (!xyPoints[0][0]) {
                tmpPoints = xyPoints;
                xyPoints=[];
                for (i = 0; i < tmpPoints.length; i+=1 ){
                    xyPoints[i] = [tmpPoints[i],tmpPoints[i]];
                }
            } else {
                tmpPoints=[];
                for (i = 0; i < xyPoints.length; i+=1 ){
                    tmpPoints[i] = xyPoints[i][0];
                }
            }
            graphHeight = parseInt($(graphId).height(), 10);
            graphWidth = parseInt($(graphId).width(), 10);

            if (LOCK5MAIN.doublePlotProperties && (!options || options.useOldAxes)) {
                graphProperties = LOCK5MAIN.doublePlotProperties;
            } else {
                minmax = $.findMinMax(xyPoints,0);
                graphProperties = {
                    xaxis: {
                        // histogram sorts points backwards
                        min: minmax.min,
                        max: minmax.max
                    },
                    grid: {
                        tickColor: "#eee",
                        hoverable: true
                    }
                };
                
                LOCK5MAIN.doublePlotProperties = graphProperties;
            }
            
            // make histogram
            k = Math.floor(graphWidth / LOCK5MAIN.PIXELSPERDOT);

            maxBucket = 0;
            mean = 0;
            $.each(xyPoints, function(i, points) {
                var val=points[0], b;
                // calculate bucket
                b = Math.floor((val - graphProperties.xaxis.min) / (graphProperties.xaxis.max - graphProperties.xaxis.min) * k);
                if (!bucketCount[b]) {
                    bucketCount[b] = 0;
                }
                histoPoints[i] = [ graphProperties.xaxis.min+b*(graphProperties.xaxis.max-graphProperties.xaxis.min)/k, bucketCount[b], val];
                bucketCount[b] = bucketCount[b] + 1;
                if (maxBucket < bucketCount[b]) {
                    maxBucket = bucketCount[b];
                }
                mean = mean+val;
            });
            mean=mean/xyPoints.length;
            if (options && !options.useOldAxes) {
                
                graphProperties.yaxis = {
                    min: -1,
                    max: Math.floor(parseInt($("#original-sample-plot").height(), 10) / LOCK5MAIN.PIXELSPERDOT)
                };
                
                if (graphProperties.yaxis.max < maxBucket*1.1) {
                    graphProperties.yaxis.max = maxBucket*1.1;
                }
                LOCK5MAIN.doublePlotProperties = graphProperties;
            }

            histoPoints = LOCK5MAIN.formatPoints(histoPoints, "#69580B", null);

        }
        if (tmpPoints) {
            thePlot = $.plot($(graphId), [histoPoints], graphProperties);
            
            if ($("#stats",$(graphId).parent()).length === 0) {
                $(graphId).before($("<div id='stats' class='stats'><\/div>"));
            }
            $("#stats",$(graphId).parent()).addClass("small").html("$n = " + tmpPoints.length + "\\ mean = " + Math.truncate(mean,3) + "$<br\/>$median = "+ Math.truncate($.median(tmpPoints),3) + "\\ stdev = "+Math.truncate(stdevFn(tmpPoints),3) + "$");
            MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
            
        } else {
            thePlot = $.plot($(graphId), [histoPoints]);
        }
        if (xyPoints.length > 0) {
            $.each([{
                mean: mean,
                color: "#000",
                y: 0
            }], function(i,mean) {
                var offset, arrowHeight, arrowWidth, textWidth, canvasContext,
                text,color;
                text = Math.truncate(mean.mean,3);
                color = mean.color;
                arrowHeight = 20;
                arrowWidth = 6;
                offset = thePlot.pointOffset({
                    x: mean.mean,
                    y: mean.y
                });

                textWidth = $.calculateSize(text).width;

                thePlot.getPlaceholder().append("<div class=\"arrow-label\" class=\"ui-dialog-content\" style=\"position:absolute;left:" + (offset.left - textWidth / 2) + "px;top:" + (offset.top + arrowHeight) + "px;\">" + text + "<\/div>");

                //plot subplot labels

                // This part does the little arrow drawing
                canvasContext = thePlot.getCanvas().getContext("2d");
                canvasContext.beginPath();
                canvasContext.moveTo(offset.left, offset.top);
                canvasContext.lineTo(offset.left - arrowWidth / 2, offset.top + arrowHeight);
                canvasContext.lineTo(offset.left + arrowWidth / 2, offset.top + arrowHeight);
                canvasContext.lineTo(offset.left, offset.top);
                canvasContext.fillStyle = color;
                canvasContext.fill();
            });
        }
        //$(".legendColorBox",$(graphId)).show();
        $(".legendColorBox",$(graphId)).last().hide();

        ( function() {

            $(graphId + " div.legend").children().hover( function(e) {
                $(this).hide();
            });
            $(graphId + " div.graph-label").hover( function(e) {
                $(this).hide();
            });
            $(graphId + " div.arrow-label").hover( function(e) {
                $(this).hide();
            });
            $(graphId).hover(null, function(e) {
                $(graphId + " div.legend").children().show();
                $(graphId + " div.graph-label").show();
                $(graphId + " div.arrow-label").show();
            });
        }());
        return thePlot;
    },
    /*
     * showTooltip: function to plot x and y as a point on a hover event
     *
     * returns: true
     *
     * parameters: x,y (Floats): relative pixel position on plot contents: (String)
     * the message to display on the plot
     */
    showTooltip: function(x, y, contents) {
        $("<div id=\"tooltip\">" + contents + "<\/div>").css({
            position: "absolute",
            top: y - 25,
            left: x + 5,
            border: "1px solid #fdd",
            padding: "2px",
            "background-color": "#eee",
            opacity: 1
        }).appendTo("body");
        return true;
    },
    /**
     * resizePlots: function that resizes all the plots based on the size of the
     * window
     */
    resizePlots: function() {
        var availableWidth, availableHeight, availableScatterHeight, pageWhitespaceWidth, pageWhitespaceHeight;
        availableHeight = $(window).height() - 365;
        $("#dotplot").height(availableHeight);
        $("#original-sample-plot").height(availableHeight / 2);
        $("#current-sample-plot").height(availableHeight / 2);

        // Calculate the width of the amount of space available to draw in
        // That's defined by counting up all the page and body margins/padding
        // then subtracting off the width of the window
        pageWhitespaceWidth = $("body").outerWidth(true) - $("body").width() + $("#page")
        .outerWidth(true) - $("#page").width();

        availableWidth = $(window).width() - pageWhitespaceWidth;
        if (availableWidth < LOCK5MAIN.MINPAGEWIDTH) {
            availableWidth = LOCK5MAIN.MINPAGEWIDTH;
        }
        $("#page").width(availableWidth);

        // Calculate the available height without scrolling off the page
        // Equal to the page, body, and header margins/padding, then subtracting
        // off the height of the window
        pageWhitespaceHeight = $("body").outerHeight(true) - $("body").height() + $("#page")
        .outerHeight(true) - $("#page").height() + $("#header")
        .outerHeight(true) - $("#header").height() + $("#footer").outerHeight(true);

        // Set the height of the page just right
        $("#page").height($(window).height() - pageWhitespaceHeight);

        // Available height is the page height minus the header height minus
        // the footer height
        availableHeight = $("#page").height() - $("#header").outerHeight(true) - $("#footer")
        .outerHeight(true) - $("#main-plot-header").outerHeight(true);

        $("#dotplot").height(availableHeight);
        $("#main-plot-graph, #dotplot")
        .width(availableWidth * 0.61 - ($("#main-plot-div")
            .outerWidth(true) - $("#main-plot-div").width()));

        // Determine available space for 2 scatter plots
        availableScatterHeight = availableHeight - $("#original-sample-header")
        .outerHeight(true) - $("#stats").outerHeight(true)- $("#hover-sample-header").outerHeight(true);

        // If a single plot will be shorter than the predefined min height, add
        // more space
        // to the page and to the dotplot so it matches
        if (availableScatterHeight < LOCK5MAIN.MINSCATTERHEIGHT * 2) {
            $("#page")
            .height($("#page").height() + (2 * LOCK5MAIN.MINSCATTERHEIGHT - availableScatterHeight));
            $("#dotplot")
            .height($("#dotplot").height() + (2 * LOCK5MAIN.MINSCATTERHEIGHT - availableScatterHeight));
            availableScatterHeight = LOCK5MAIN.MINSCATTERHEIGHT * 2;
        }

        $("#original-sample-plot").height(availableScatterHeight * 0.5);
        $("#current-sample-plot").height(availableScatterHeight * 0.5);

        $("#original-sample-plot")
        .width(availableWidth * 0.37 - ($("#scatter-plot-div")
            .outerWidth(true) - $("#scatter-plot-div").width()));
        $("#current-sample-plot")
        .width(availableWidth * 0.37 - ($("#scatter-plot-div")
            .outerWidth(true) - $("#scatter-plot-div").width()));

        // Clamp scatter plot widths at no greater that 1.62 times their height
        if ($("#original-sample-plot").width() > $("#original-sample-plot")
        .height() * 1.67) {
            // Since there is extra space, make dotplot wider
            $("#main-plot-graph, #dotplot")
            .width($("#dotplot").width() + $("#original-sample-plot")
            .width() - $("#original-sample-plot").height() * 1.67);

            $("#original-sample-plot")
            .width($("#original-sample-plot").height() * 1.67);
            $("#current-sample-plot")
            .width($("#current-sample-plot").height() * 1.67);

        }

        // Move the tail controls so they sit nicely on the dotplot
        $("#tail-controls").css({
            left: $("#dotplot").offset().left + 25,
            top: $("#dotplot").offset().top + 10
        });

        if (LOCK5MAIN.activeHistogram.histogramPoints) {
            if (!LOCK5MAIN.activeHistogram.two_plot) {
                LOCK5MAIN.dotPlot.plot("#dotplot", LOCK5MAIN.activeHistogram.histogramPoints);
                LOCK5MAIN.hoverPlot("#current-sample-plot", []);
                LOCK5MAIN.plot("#original-sample-plot", LOCK5MAIN.ORIGINAL_SAMPLE.data, LOCK5MAIN
                .generateGraphBounds(LOCK5MAIN.ORIGINAL_SAMPLE.data, {
                    xaxis: {
                        axisLabel: LOCK5MAIN.ORIGINAL_SAMPLE.domain,
                        axisLabelUseCanvas: true,
                        axisLabelFontSizePixels: 10,
                        axisLabelFontFamily: 'Arial'
                    },
                    yaxis: {
                        axisLabel: LOCK5MAIN.ORIGINAL_SAMPLE.range,
                        axisLabelUseCanvas: true,
                        axisLabelFontSizePixels: 10,
                        axisLabelFontFamily: 'Arial'
                    }
                }));
            } else {
                LOCK5MAIN.dotPlot.plot("#dotplot", LOCK5MAIN.activeHistogram.histogramPoints);
                if (LOCK5MAIN.activeHistogram.two_plot_single) {
                    LOCK5MAIN.singlePlot("#current-sample-plot", []);
                    LOCK5MAIN.singlePlot("#original-sample-plot", LOCK5MAIN.ORIGINAL_SAMPLE.data, {
                        labels: LOCK5MAIN.ORIGINAL_SAMPLE.label
                    });
                } else {
                    LOCK5MAIN.doublePlot("#current-sample-plot", []);
                    LOCK5MAIN.doublePlot("#original-sample-plot", LOCK5MAIN.ORIGINAL_SAMPLE.data, {
                        labels: LOCK5MAIN.ORIGINAL_SAMPLE.label
                    });
                }
            }
        }
    },
    /*
     * hoverPlot: plots the given series on a hover event
     *
     * returns true
     *
     * parameters: id (String) -- div id of the container to generate the plot in
     * series (Array of [x,y] float): the points to display in the plot
     */
    hoverPlot: function(id, series) {
        LOCK5MAIN.plot(id, series, LOCK5MAIN
        .generateGraphBounds(LOCK5MAIN.ORIGINAL_SAMPLE.data, {
            // These are extra arguments for generating axis labels
            xaxis: {
                axisLabel: LOCK5MAIN.ORIGINAL_SAMPLE.domain,
                axisLabelUseCanvas: true,
                axisLabelFontSizePixels: 10,
                axisLabelFontFamily: 'Arial'
            },
            yaxis: {
                axisLabel: LOCK5MAIN.ORIGINAL_SAMPLE.range,
                axisLabelUseCanvas: true,
                axisLabelFontSizePixels: 10,
                axisLabelFontFamily: 'Arial'
            }
        }));
        return true;
    },
    /*
     * makeTable: generates a table for the given series on a hover event
     *
     * returns true
     *
     * parameters: id (String) -- div id of the container to generate the table in
     * dataset (Object): containing count and n
     */
    makeTable: function(id, dataset) {
        $(id).empty();
        $(id).append("<div class='ui-grid ui-widget ui-widget-content ui-corner-all'>" + "<table class='ui-grid-content ui-widget-content'>" + "<thead><tr><th>Count<\/th><th>n<\/th><th>Proportion<\/th><\/tr><\/thead>" + "<tr><td class='ui-widget-content'>" + dataset.count + "<\/td><td class='ui-widget-content'>" + dataset.n + "<\/td><td class='ui-widget-content'>" + (Math.truncate(Math.round(dataset.count*1000.0/ dataset.n)/1000,3)) + "<\/td><\/tr><\/table><\/div>");
        return true;
    },
    /*
     * makeTwoCatTable: generates a table for the given series on a hover event
     *
     * returns true
     *
     * parameters: id (String) -- div id of the container to generate the table in
     * dataset (Object): containing count and n
     */
    makeTwoCatTable: function(id, dataset) {
        var i, table;
        table = "<div class='ui-grid ui-widget ui-widget-content ui-corner-all'>" + "<table class='ui-grid-content ui-widget-content'>" + "<thead><tr><th>Group<\/th><th>Count<\/th><th>n<\/th><th>Proportion<\/th><\/tr><\/thead>";
        for (i = 0; i <= 1; i += 1) {
            table = table + "<tr><td class='ui-widget-content'>" + dataset[i].label + "<\/td><td class='ui-widget-content'>" + dataset[i].count + "<\/td><td class='ui-widget-content'>" + dataset[i].n + "<\/td><td class='ui-widget-content'>" + Math.truncate(dataset[i].count / dataset[i].n,2) + "<\/td><\/tr>";
        }
        table = table + "<tr><td class='ui-widget-content'>"+dataset[0].label+"-"+dataset[1].label+"<\/td><td class='ui-widget-content'>" + (dataset[0].count-dataset[1].count) + "<\/td><td class='ui-widget-content'>n/a<\/td><td class='ui-widget-content'>" + Math.truncate(dataset[0].count / dataset[0].n - dataset[1].count / dataset[1].n,2) + "<\/td><\/tr>";
        table = table + "<\/table><\/div>"; 
        $(id).empty();
        $(id).append(table);
        return true;
    },
    /*
     * startup: shared application code initialize the graph
     */
    startup: function(options) {
        // create all the buttons and buttonsets
        $("button").button();
        $(".menu-item").removeClass("ui-corner-left ui-corner-right ui-corner-all");

        if (typeof options.parseCSV === 'undefined' && typeof options.parseCSV_two_datasets === 'undefined') {
            options.parseCSV = true;
        }

        // get the first data set and initialize the system with it
        $.ajax({
            type: 'GET',
            url: 'datasets.txt',
            dataType: "json",
            success: function(data) {
                LOCK5MAIN.DATASETS = data;
                if (options.parseCSV) {
                    $.ajax({
                        type: "GET",
                        url: LOCK5MAIN.DATASETS[0].source,
                        dataType: "html",
                        success: ( function(domain, range, label) {
                            return function(data) {
                                LOCK5MAIN.initialize(($.parseCSV(domain, range)
                                    (data)), domain, range, label, LOCK5MAIN.DATASETS[0]);
                                LOCK5MAIN.resizePlots();
                            };
                        }
                        (LOCK5MAIN.DATASETS[0].domain, LOCK5MAIN.DATASETS[0].range, LOCK5MAIN.DATASETS[0].label))
                    });
                } else if (options.parseCSV_two_datasets) {
                    // This will parse out the first two datasets for the 1
                    // quant 1 cat example
                    $.ajax({
                        type: "GET",
                        url: LOCK5MAIN.DATASETS[0].source,
                        dataType: "html",
                        success: function(data) {
                            var datasets = ($.parseFullCSVNoHeader(",")(data)), columns = [[],[]], categories=[];
                            $.each(datasets.points,function(i,val) {
                                if ($.inArray(val[0],categories) === -1) {
                                    categories.push(val[0]); 
                                }
                                columns[$.inArray(val[0],categories)].push(val[1]);
                            });
                            LOCK5MAIN.initialize([ columns[0],columns[1] ],
                                [ categories[0],categories[1] ], [ categories[0],categories[1] ]);
                            LOCK5MAIN.resizePlots();
                        }
                    });
                } else {
                    LOCK5MAIN.initialize(LOCK5MAIN.DATASETS[0]);
                }
                if (options.onLoad) {
                    options.onLoad();
                }
                // set up data pop-up menu
                var menuItems = [];
                $.each(LOCK5MAIN.DATASETS, function(i, dataset) {
                    menuItems.push({
                        label: dataset.label,
                        callback: function() {
                            if (options.parseCSV) {
                                $.ajax({
                                    type: "GET",
                                    url: dataset.source,
                                    cache: true,
                                    dataType: "text",
                                    success: ( function(domain, range, label) {
                                        return function(data) {
                                            LOCK5MAIN.initialize($.parseCSV(domain, range)(data), domain, range, label, dataset);
                                        };
                                    }(dataset.domain, dataset.range, dataset.label)),
                                    error: function(jqXHR, textStatus, errorThrown) {
                                        console.log(errorThrown);
                                    }
                                });
                            } else if (options.parseCSV_two_datasets) {
                                $.ajax({
                                    type: "GET",
                                    url: dataset.source,
                                    cache: true,
                                    dataType: "text",
                                    success: function(data) {
                                        var datasets = ($.parseFullCSVNoHeader(",")(data)), columns = [[],[]], categories=[];
                                        $.each(datasets.points,function(i,val) {
                                            if ($.inArray(val[0],categories) === -1) {
                                                categories.push(val[0]); 
                                            }
                                            columns[$.inArray(val[0],categories)].push(val[1]);
                                        });
                                        LOCK5MAIN.initialize([ columns[0],columns[1] ],
                                            [ categories[0],categories[1] ], [ categories[0],categories[1] ]);
                                        LOCK5MAIN.resizePlots();
                                    },
                                    error: function(jqXHR, textStatus, errorThrown) {
                                        console.log(errorThrown);
                                    }
                                });
                            } else {
                                LOCK5MAIN.initialize(dataset);
                            }
                            // Call external callback to indicate
                            // load complete
                            if (options.onLoad) {
                                options.onLoad();
                            }
                        }
                    });
                });
                $.createPopupMenu($("#data-source-button"), menuItems);
            }
        });
        LOCK5MAIN.dotPlot = $.createDotplot({
            id: "#dotplot",
            sampleType: options.sampleType,
            graphAnnotation: options.graphAnnotation
        });

        $(window).resize(LOCK5MAIN.resizePlots);

        // jquery ui setup
        $("button#sample-reset-button").click(LOCK5MAIN.clearGraph);
        $("#original-sample-plot").bind("plothover", LOCK5MAIN.hoverPoint);
        $("#current-sample-plot").bind("plothover", LOCK5MAIN.hoverPoint);
        $("#dotplot").bind("plothover", LOCK5MAIN.hoverGraph("#current-sample-plot"));
        // Create the generate sample buttons
        var generateSampleButtonValues = [ 1, 10, 100, 1000 ];
        $.each(generateSampleButtonValues, function(i, value) {
            $("#sample-generator-buttonset")
            .append($("<button>Generate " + value + " Samples<\/button>")
            .button().click(options.generateAndPlot(value)));
        });
        // create LOCK5MAIN buttonset for 1, 10, 100, 1000 generations
        $("#sample-generator-buttonset").buttonset();

        // button to show data source for single source controls
        $("#data-source-controls button#show-data-source-button").button().click( function() {
            var templates, table;
            templates = {
                header: "<thead><tr><th>#{cell1}<\/th><th>#{cell2}<\/th><\/tr><\/thead>",
                row: "<tr><td>#{cell1}<\/td><td>#{cell2}<\/td><\/tr>"
            };

            table = "<div><table id=\"datatable\" class=\"tablesorter\">";
            table += $.tmpl(templates.header, {
                cell1: LOCK5MAIN.ORIGINAL_SAMPLE.domain,
                cell2: LOCK5MAIN.ORIGINAL_SAMPLE.range
            });
            table += "<tbody>";
            $.each(LOCK5MAIN.ORIGINAL_SAMPLE.data, function(i, value) {
                table += $.tmpl(templates.row, {
                    cell1: value[0],
                    cell2: value[1]
                });
            });
            table += "<\/tbody><\/table><\/div>";

            $("#datatabledialog").dialog("close");

            $(table).dialog({
                modal: true,
                create: function(event, ui) {
                    $("#datatable").tablesorter();
                },
                beforeClose: function(event, ui) {
                    $("#datatabledialog").remove();
                },
                title: LOCK5MAIN.ORIGINAL_SAMPLE.label,
                height: ($.calculateSize(table).height + 55 > 750) ? 750 : $
                .calculateSize(table).height + 55
            }).attr("id", "datatabledialog");
            return false;
        });
        // button to edit data
        $("#data-source-controls button#edit-data-source-button").button().click( function() {
            var dataEditor, dataAsText, parseForm;

            parseForm = function(form) {
                return function() {
                    try {
                        var data = $.parseFullCSV(",")($("#edit-data-source-textarea", form).val());
                        if (data.points.length > 0) {
                            LOCK5MAIN.initialize(data.points, data.domain, data.range, "Custom Dataset");
                            $("#data-source-button").button("option", "label", "Custom Dataset");
                            form.dialog("close");
                        }
                        throw "0 sets of points parsed";
                    } catch (e) {
                        $(".error", form).show();
                    }
                };
            };
            dataAsText = LOCK5MAIN.ORIGINAL_SAMPLE.domain + "," + LOCK5MAIN.ORIGINAL_SAMPLE.range + "\n";
            $.each(LOCK5MAIN.ORIGINAL_SAMPLE.data, function(i, value) {
                dataAsText += value[0] + "," + value[1] + "\n";
            });
            dataEditor = $('<div><textarea id="edit-data-source-textarea" rows="' + (LOCK5MAIN.ORIGINAL_SAMPLE.data.length + 1) + '" cols="20">' + dataAsText + '<\/textarea><p>Manually edit the values above or paste a tab or comma seperated file into the box and click Ok.  The file must have only two columns and include column headers.<\/p><p class="error ui-state-error">Error: Improper format.<\/p><\/div>')
            .dialog({
                create: function() {
                    $(".error", this).hide();
                },
                modal: true
            });
            $(dataEditor).dialog({
                buttons: [{
                    text: "Ok",
                    click: parseForm(dataEditor)
                } ],
                title: "Edit data"
            }).attr("id", "dataeditordialog");
            return false;

        });
        // button to show data source for dual source controls (1 cat/1 quant)
            $("#data-source-controls_two_datasets button#show-data-source-button").button().click( function() {
                var templates, table, i, j;
                templates = {
                    header: "<thead><tr><th>#{cell1}<\/th><th>#{cell2}<\/th><\/tr><\/thead>",
                    row: "<tr><td>#{cell1}<\/td><td>#{cell2}<\/td><\/tr>"
                };

                table = "<div><table id=\"datatable\" class=\"tablesorter\">";
                table += $.tmpl(templates.header, {
                    cell1: "Binary Categorical Variable",
                    cell2: "Quantitative Variable"
                });
                table += "<tbody>";
                for (i = 0; i < LOCK5MAIN.ORIGINAL_SAMPLE.data.length; i += 1) {
                    for (j = 0; j < LOCK5MAIN.ORIGINAL_SAMPLE.data[i].length; j += 1) {
                        table += $.tmpl(templates.row, {
                            cell1: LOCK5MAIN.ORIGINAL_SAMPLE.label[i],
                            cell2: LOCK5MAIN.ORIGINAL_SAMPLE.data[i][j]
                        });
                    }
                }
                table += "<\/tbody><\/table><\/div>";

                $("#datatabledialog").dialog("close");

                $(table).dialog({
                    modal: true,
                    create: function(event, ui) {
                        $("#datatable").tablesorter();
                    },
                    beforeClose: function(event, ui) {
                        $("#datatabledialog").remove();
                    },
                    title: "One Binary Categorical One Quantiative Variable",
                    height: ($.calculateSize(table).height + 55 > 750) ? 750 : $.calculateSize(table).height + 55
                }).attr("id", "datatabledialog");
                return false;
            });
            // button to edit data for 1_quant_1_cat 
            $("#data-source-controls_two_datasets #edit-data-source-button").button().click( function() {
                var dataEditor, dataAsText, parseForm, i, j;

                parseForm = function(form) {
                    return function() {
                        try {
                                var datasets = ($.parseFullCSVNoHeader(",")($("#edit-data-source-textarea", form).val())), columns = [[],[]], categories=[];
                                $.each(datasets.points,function(i,val) {
                                    if ($.inArray(val[0],categories) === -1) {
                                        categories.push(val[0]); 
                                    }
                                    columns[$.inArray(val[0],categories)].push(val[1]);
                                });
                                LOCK5MAIN.initialize([ columns[0],columns[1] ],
                                    [ categories[0],categories[1] ], [ categories[0],categories[1] ]);
                                LOCK5MAIN.resizePlots();
                                $("#data-source-controls_two_datasets #data-source-button").button("option", "label", "Custom Dataset");
                                form.dialog("close");
                        } catch (e) {
                            $(".error", form).show();
                        }
                    };
                };
                
                //build up the current dataset to show in the text box
                dataAsText = "";
                for (i = 0; i < LOCK5MAIN.ORIGINAL_SAMPLE.data.length; i += 1) {
                    for (j = 0; j < LOCK5MAIN.ORIGINAL_SAMPLE.data[i].length; j += 1) {
                        dataAsText += LOCK5MAIN.ORIGINAL_SAMPLE.label[i] + ", " + LOCK5MAIN.ORIGINAL_SAMPLE.data[i][j] + "\n";
                    }
                }
                dataEditor = $('<div><textarea id="edit-data-source-textarea" rows="' + 20 + '" cols="20">' + dataAsText + '<\/textarea><p>Manually edit the values above or paste a tab or comma seperated file into the box and click Ok.  The file must have only two columns.<\/p><p class="error ui-state-error">Error: Improper format.<\/p><\/div>')
                .dialog({
                    create: function() {
                        $(".error", this).hide();
                    },
                    modal: true
                });
                dataEditor.dialog({
                    buttons: [{
                        text: "Ok",
                        click: parseForm(dataEditor)
                    } ],
                    title: "Edit data"
                }).attr("id", "dataeditordialog");
                return false;
        });
        
        // button to show data source for one quant controls
            $("#data-source-controls_one_quant button#show-data-source-button").button().click( function() {
                var templates, table, i, j;
                templates = {
                    header: "<thead><tr><th>#{cell1}<\/th><\/tr><\/thead>",
                    row: "<tr><td>#{cell1}<\/td><\/tr>"
                };

                table = "<div><table id=\"datatable\" class=\"tablesorter\">";
                table += $.tmpl(templates.header, {
                    cell1: LOCK5MAIN.ORIGINAL_SAMPLE.variable
                });
                table += "<tbody>";
                for (i = 0; i < LOCK5MAIN.ORIGINAL_SAMPLE.data.length; i += 1) {
                    for (j = 0; j < LOCK5MAIN.ORIGINAL_SAMPLE.data[i].length; j += 1) {
                        table += $.tmpl(templates.row, {
                            cell1: LOCK5MAIN.ORIGINAL_SAMPLE.data[i][0]
                        });
                    }
                }
                table += "<\/tbody><\/table><\/div>";

                $("#datatabledialog").dialog("close");

                $(table).dialog({
                    modal: true,
                    create: function(event, ui) {
                        $("#datatable").tablesorter();
                    },
                    beforeClose: function(event, ui) {
                        $("#datatabledialog").remove();
                    },
                    title: "One Quantiative Variable",
                    height: ($.calculateSize(table).height + 55 > 750) ? 750 : $.calculateSize(table).height + 55
                }).attr("id", "datatabledialog");
                return false;
            });
            
            //Edit controls for one quantitative
            $("#data-source-controls_one_quant #edit-data-source-button").button().click( function() {
                var dataEditor, dataAsText, parseForm, i, j;

                parseForm = function(form) {
                    return function() {
                        try {
                                var datasets = ($.parseFullCSVNoHeader(",")($("#edit-data-source-textarea", form).val())), columns = [[],[]], categories=[];
                                LOCK5MAIN.initialize(datasets.points, "Custom", null, "Custom Dataset");
                                LOCK5MAIN.resizePlots();
                                $("#data-source-controls_one_quant #data-source-button").button("option", "label", "Custom Dataset");
                                form.dialog("close");
                        } catch (e) {
                            $(".error", form).show();
                        }
                    };
                };
                
                //build up the current dataset to show in the text box
                dataAsText = "";
                for (i = 0; i < LOCK5MAIN.ORIGINAL_SAMPLE.data.length; i += 1) {
                        dataAsText += LOCK5MAIN.ORIGINAL_SAMPLE.data[i][0] + "\n";
                }
                dataEditor = $('<div><textarea id="edit-data-source-textarea" rows="' + 20 + '" cols="20">' + dataAsText + '<\/textarea><p>Manually edit the values above or paste a tab or comma seperated file into the box and click Ok.  The file must have only one column.<\/p><p class="error ui-state-error">Error: Improper format.<\/p><\/div>')
                .dialog({
                    create: function() {
                        $(".error", this).hide();
                    },
                    modal: true
                });
                dataEditor.dialog({
                    buttons: [{
                        text: "Ok",
                        click: parseForm(dataEditor)
                    } ],
                    title: "Edit data"
                }).attr("id", "dataeditordialog");
                return false;
        });
        
        LOCK5MAIN.resizePlots();
    }
};